package implements

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"io"
	"net/http"
	"sort"
	"strings"

	"gitee.com/bitwormhole/cloud-aliyun-api/aliyun/openapi"
	"github.com/starter-go/base/lang"
	"github.com/starter-go/httpagent"
	"github.com/starter-go/vlog"
)

////////////////////////////////////////////////////////////////////////////////

type openapiCanonicalRequestBuilder struct {

	// 初始化变量

	want *openapi.Request

	// 凭证

	AccessKeyID     string
	AccessKeySecret []byte

	// 中间变量

	// CanonicalRequest =
	// HTTPRequestMethod + '\n' +    //http方法名，全大写
	// CanonicalURI + '\n' +         //规范化URI
	// CanonicalQueryString + '\n' + //规范化查询字符串
	// CanonicalHeaders + '\n' +     //规范化消息头
	// SignedHeaders + '\n' +        //已签名消息头
	// HashedRequestPayload

	HTTPRequestMethod    string
	CanonicalURI         string // aka URL.path
	CanonicalQueryString string
	CanonicalHeaders     string
	SignedHeaders        string
	HashedRequestPayload string

	// 签名结果
	Authorization *openapi.Header
}

func (inst *openapiCanonicalRequestBuilder) init(req *openapi.Request) error {

	steps := make([]func(int, *openapi.Request) error, 0)

	steps = append(steps, inst.initMethod)
	steps = append(steps, inst.initPath)
	steps = append(steps, inst.initQuery)
	steps = append(steps, inst.initHeaders)
	steps = append(steps, inst.initSignedHeaders)
	steps = append(steps, inst.initPayload)

	for i, fn := range steps {
		err := fn(i, req)
		if err != nil {
			return err
		}
	}

	inst.want = req
	return nil
}

func (inst *openapiCanonicalRequestBuilder) initMethod(step int, req *openapi.Request) error {
	method := req.HTTPMethod
	method = strings.TrimSpace(method)
	if method == "" {
		method = http.MethodGet
	}
	method = strings.ToUpper(method)
	inst.HTTPRequestMethod = method
	return nil
}

func (inst *openapiCanonicalRequestBuilder) initPath(step int, req *openapi.Request) error {
	pathElements := req.PathParameters
	strb := new(strings.Builder)
	for _, el := range pathElements {
		str := inst.encodeAsRFC3986(el)
		strb.WriteString("/")
		strb.WriteString(str)
	}
	if strb.Len() == 0 {
		inst.CanonicalURI = "/"
	} else {
		inst.CanonicalURI = strb.String()
	}
	return nil
}

func (inst *openapiCanonicalRequestBuilder) initQuery(step int, req *openapi.Request) error {
	src := req.QueryParameters
	dst := make([]string, 0)
	for _, item := range src {
		kv := strings.SplitN(item, "=", 2)
		if len(kv) == 2 {
			name := inst.encodeAsRFC3986(kv[0])
			value := inst.encodeAsRFC3986(kv[1])
			dst = append(dst, name+"="+value)
		} else if len(kv) == 1 {
			name := inst.encodeAsRFC3986(kv[0])
			dst = append(dst, name+"=")
		}
	}
	sort.Strings(dst)
	strb := new(strings.Builder)
	for i, item := range dst {
		if i > 0 {
			strb.WriteRune('&')
		}
		strb.WriteString(item)
	}
	inst.CanonicalQueryString = strb.String()
	return nil
}

func (inst *openapiCanonicalRequestBuilder) initHeaders(step int, req *openapi.Request) error {

	src := req.RequestHeaders
	dst := make([]string, 0)
	sb := &strings.Builder{}
	namelist := make([]string, 0)

	for _, h := range src {

		values := h.Values
		for i, val := range values {
			v2 := strings.TrimSpace(val)
			values[i] = v2
		}
		sort.Strings(values)

		name := h.Name
		name = strings.ToLower(name)
		name = strings.TrimSpace(name)

		sb.Reset()
		sb.WriteString(name)
		sb.WriteString(":")
		namelist = append(namelist, name)

		for i, val := range values {
			if i > 0 {
				sb.WriteString(",")
			}
			sb.WriteString(val)
		}

		sb.WriteString("\n")
		dst = append(dst, sb.String())
	}

	// for headers
	sort.Strings(dst)
	sb.Reset()
	for _, item := range dst {
		sb.WriteString(item)
	}
	inst.CanonicalHeaders = sb.String()

	// for namelist
	sort.Strings(namelist)
	sb.Reset()
	for i, item := range namelist {
		if i > 0 {
			sb.WriteRune(';')
		}
		sb.WriteString(item)
	}
	inst.SignedHeaders = sb.String()

	return nil
}

func (inst *openapiCanonicalRequestBuilder) initSignedHeaders(step int, req *openapi.Request) error {
	// nop: impl in func initHeaders
	return nil
}

func (inst *openapiCanonicalRequestBuilder) initPayload(step int, req *openapi.Request) error {
	sum := computePayloadSum(req)
	inst.HashedRequestPayload = sum
	return nil
}

func (inst *openapiCanonicalRequestBuilder) encodeAsRFC3986(str string) string {
	vlog.Warn("todo: no impl of encodeAsRFC3986")
	return str
}

func (inst *openapiCanonicalRequestBuilder) sign() error {

	// 构造 CanonicalRequest
	const nl = "\n"
	CanonicalRequestBuilder := new(strings.Builder)
	CanonicalRequestBuilder.WriteString(inst.HTTPRequestMethod + nl)
	CanonicalRequestBuilder.WriteString(inst.CanonicalURI + nl)
	CanonicalRequestBuilder.WriteString(inst.CanonicalQueryString + nl)
	CanonicalRequestBuilder.WriteString(inst.CanonicalHeaders + nl)
	CanonicalRequestBuilder.WriteString(inst.SignedHeaders + nl)
	CanonicalRequestBuilder.WriteString(inst.HashedRequestPayload)
	cr := CanonicalRequestBuilder.String()
	CanonicalRequest := []byte(cr)

	// vlog.Warn("client CanonicalRequest is [%s]", cr)

	// 准备签名数据
	sum1 := sha256.Sum256(CanonicalRequest)
	hex1 := lang.HexFromBytes(sum1[:])
	HashedCanonicalRequest := hex1.String()
	SignatureAlgorithm := "ACS3-HMAC-SHA256"
	StringToSign := SignatureAlgorithm + "\n" + HashedCanonicalRequest

	// vlog.Warn("client StringToSign is [%s]", StringToSign)

	// 签名
	SignatureMethod := hmac.New(sha256.New, inst.AccessKeySecret)
	SignatureMethod.Write([]byte(StringToSign))
	sum2 := SignatureMethod.Sum(nil)
	hex2 := lang.HexFromBytes(sum2)
	Signature := hex2.String()

	// 将签名添加到请求中
	AuthorizationBuilder := new(strings.Builder)
	AuthorizationBuilder.WriteString(SignatureAlgorithm)
	AuthorizationBuilder.WriteString(" Credential=")
	AuthorizationBuilder.WriteString(inst.AccessKeyID)
	AuthorizationBuilder.WriteString(",SignedHeaders=")
	AuthorizationBuilder.WriteString(inst.SignedHeaders)
	AuthorizationBuilder.WriteString(",Signature=")
	AuthorizationBuilder.WriteString(Signature)
	AuthorizationValue := AuthorizationBuilder.String()
	Authorization := new(openapi.Header)
	Authorization.Name = "Authorization"
	Authorization.Values = []string{AuthorizationValue}
	inst.Authorization = Authorization

	return nil
}

func (inst *openapiCanonicalRequestBuilder) create(c context.Context) (*httpagent.Request, error) {

	err := inst.updateHeaders()
	if err != nil {
		return nil, err
	}

	err = inst.sign()
	if err != nil {
		return nil, err
	}

	dst := new(httpagent.Request)
	dst.Context = c
	dst.Method = inst.HTTPRequestMethod
	dst.URL = inst.createURL()
	dst.Headers = inst.createHeaders()
	dst.Body = inst.createBody()
	return dst, nil
}

// 在签名之前，更新一些必要的头部字段
func (inst *openapiCanonicalRequestBuilder) updateHeaders() error {

	// req := inst.want
	// req.SetHeader(openapi.XACSContentSHA256, sum)
	// inst.HashedRequestPayload = sum

	return nil
}

func (inst *openapiCanonicalRequestBuilder) createURL() string {

	scheme := inst.want.Protocol
	host := inst.want.Endpoint
	path := inst.CanonicalURI
	query := inst.CanonicalQueryString

	scheme = strings.ToLower(scheme)
	if scheme != "http" {
		scheme = "https"
	}

	b := new(strings.Builder)
	b.WriteString(scheme)
	b.WriteString("://")
	b.WriteString(host)
	b.WriteString(path)
	if len(query) > 0 {
		b.WriteString("?")
		b.WriteString(query)
	}
	url := b.String()
	return url
}

func (inst *openapiCanonicalRequestBuilder) createHeaders() httpagent.Headers {
	// src := inst.want.RequestHeaders
	want := inst.want
	dst := httpagent.Headers{}
	auth := inst.Authorization
	if auth != nil {
		// src[auth.Name] = auth
		want.SetHeader(auth.Name, auth.Get())
	}
	src := want.RequestHeaders
	for _, item := range src {
		name := item.Name
		values := item.Values
		for _, val := range values {
			dst.Set(name, val)
		}
	}
	return dst
}

func (inst *openapiCanonicalRequestBuilder) createBody() io.ReadCloser {
	body := inst.want.RequestBody
	if body == nil {
		return nil
	}
	r := bytes.NewReader(body)
	return io.NopCloser(r)
}

////////////////////////////////////////////////////////////////////////////////
